package com.hmall.search.service.impl;


import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmall.common.domain.PageDTO;
import com.hmall.common.utils.CollUtils;
import com.hmall.search.domain.po.Item;
import com.hmall.search.domain.po.ItemDoc;
import com.hmall.search.domain.query.ItemPageQuery;
import com.hmall.search.domain.vo.CategoryAndBrandVo;
import com.hmall.search.mapper.SearchMapper;
import com.hmall.search.service.ISearchItemService;
import com.hmall.search.utils.EsSearchUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpHost;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.lucene.search.function.CombineFunction;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;


/**
 * <p>
 * 商品表 服务实现类
 * </p>
 *
 * @author 虎哥
 */
@Service
@Slf4j
public class SearchItemServiceImpl extends ServiceImpl<SearchMapper, Item> implements ISearchItemService {

    @Resource
    private RestHighLevelClient restHighLevelClient;

//    @Override
//    public PageDTO<ItemDoc> EsSearchTest(ItemPageQuery query) {
//        //建立连接
//        restHighLevelClient = new RestHighLevelClient(RestClient.builder(
//                HttpHost.create("http://192.168.88.133:9200")
//        ));
//        PageDTO<ItemDoc> result = new PageDTO<>();
//        //1.构造请求
//        SearchRequest searchRequest = new SearchRequest("items");
//        //2.构造查询条件
//        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
//        //精准总数
//        searchRequest.source().trackTotalHits(true);
//        if (query.getKey() != null && !"".equals(query.getKey())) {
////            searchRequest.source()
////                    .query(QueryBuilders.matchQuery("name", query.getKey()));
//            boolQueryBuilder.must(QueryBuilders.matchQuery("name", query.getKey()));
//        }
//        //高亮
//        searchRequest.source().highlighter(
//                SearchSourceBuilder.highlight()
//                        .field("name")
//                        .preTags("<em>")
//                        .postTags("</em>")
//        );
//        //分页
//        searchRequest.source().from(query.from()).size(query.getPageSize());//获取页数
//        //排序
//        if (!"".equals(query.getSortBy())) {
//            searchRequest.source().sort(query.getSortBy(), query.getIsAsc() ? SortOrder.ASC : SortOrder.DESC);
//        } else {
//            searchRequest.source().sort("updateTime", query.getIsAsc() ? SortOrder.ASC : SortOrder.DESC);
//        }
//        //分类
//        if (query.getCategory() != null && !"".equals(query.getCategory())) {
//            boolQueryBuilder.filter(QueryBuilders.termQuery("category", query.getCategory()));
////            searchRequest.source().query(QueryBuilders.termQuery("category", query.getCategory()));
//        }
//        //品牌
//        if (query.getBrand() != null && !"".equals(query.getBrand())) {
//            boolQueryBuilder.filter(QueryBuilders.termQuery("brand", query.getBrand()));
////            searchRequest.source().query(QueryBuilders.termQuery("brand", query.getBrand()));
//        }
//        //价格
//        if (query.getMinPrice() != null && query.getMaxPrice() != null) {
//            boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(query.getMinPrice()).lte(query.getMaxPrice()));
////            searchRequest.source().query(QueryBuilders.rangeQuery("price").gte(query.getMinPrice()).lte(query.getMaxPrice()));
//        }
//
//
//        //searchRequest.source().query(boolQueryBuilder);
//        //排名 广告优先
//        searchRequest.source().query(QueryBuilders.functionScoreQuery(boolQueryBuilder,
//                new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
//                        new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.termQuery("isAD", true),
//                                ScoreFunctionBuilders.weightFactorFunction(100))
//                }).boostMode(CombineFunction.MULTIPLY));
//
//        try {
//            //发起请求
//            SearchResponse search = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
//            //解析结果
//            result.setTotal(search.getHits().getTotalHits().value);
//            result.setPages(search.getHits().getTotalHits().value %
//                    query.getPageSize() == 0 ? search.getHits().getTotalHits().value / query.getPageSize()
//                    : search.getHits().getTotalHits().value / query.getPageSize() + 1);
//
//            final SearchHit[] hits = search.getHits().getHits();
//            List<ItemDoc> list = new ArrayList<>();
//            for (SearchHit hit : hits) {
//                ItemDoc itemDoc = JSONUtil.toBean(hit.getSourceAsString(), ItemDoc.class);
//                Map<String, HighlightField> hfs = hit.getHighlightFields();
//                if (CollUtils.isNotEmpty(hfs)) {
//                    // 5.1.有高亮结果，获取name的高亮结果
//                    HighlightField hf = hfs.get("name");
//                    if (hf != null) {
//                        // 5.2.获取第一个高亮结果片段，就是商品名称的高亮值
//                        String hfName = hf.getFragments()[0].string();
//                        itemDoc.setName(hfName);
//                    }
//                }
//                list.add(itemDoc);
//            }
//            result.setList(list);
//        } catch (IOException e) {
//            log.error("查询ES失败,出现异常");
//        } finally {
//            try {
//                restHighLevelClient.close();
//            } catch (IOException e) {
//                log.error("连接异常");
//            }
//        }
//        //返回
//        return result;
//    }

    /**
     * 根据查询条件在Elasticsearch中搜索商品文档，并返回分页结果。
     *
     * @param query 包含分页、排序、筛选等条件的查询对象
     * @return 包含搜索结果的分页对象，其中包含商品文档列表、总记录数和总页数
     */
    @Override
    public PageDTO<ItemDoc> EsSearch(ItemPageQuery query) {
        // 1. 初始化分页结果对象
        PageDTO<ItemDoc> result = new PageDTO<>();
        // 2. 建立与Elasticsearch的连接，使用try-with-resources确保客户端正确关闭
        try {
            restHighLevelClient = EsSearchUtils.createClient("http://192.168.88.133:9200");

            // 2.1 创建搜索请求对象，指定索引名称为"items"
            SearchRequest searchRequest = new SearchRequest("items");
            // 2.2 创建 搜索源 构建器，用于设置查询条件、分页、排序等
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            // 2.3 设置排序规则，根据得分降序排序
            sourceBuilder.sort("_score", SortOrder.DESC); // 如果没有显式排序，ES 默认按 `_score` 降序排列，此处为显式声明

            // 3. 构建查询条件
            // 3.1 根据查询对象生成布尔查询条件
            BoolQueryBuilder boolQuery = buildBoolQuery(query);
            // 3.2 使用function_score查询，优先展示广告商品，后续可扩展其他功能
            QueryBuilder finalQuery = QueryBuilders.functionScoreQuery(
                    boolQuery,
                    new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
                            new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                                    QueryBuilders.termQuery("isAD", true),
                                    ScoreFunctionBuilders.weightFactorFunction(100)
                            )
                    }
            ).boostMode(CombineFunction.MULTIPLY);
            // 重新设置查询条件
            sourceBuilder.query(finalQuery);

            // 3.3 设置高亮显示，针对商品名称字段进行高亮
            sourceBuilder.highlighter(buildHighlighter("name"));
            // 3.4 设置分页参数，从查询对象中获取起始位置和每页大小
            sourceBuilder.from(query.from()).size(query.getPageSize());
            // 3.5 设置排序字段和排序方式，默认按更新时间排序
            String sortBy = StringUtils.isNotBlank(query.getSortBy()) ? query.getSortBy() : "updateTime";
            sourceBuilder.sort(sortBy, query.getIsAsc() ? SortOrder.ASC : SortOrder.DESC);
            // 3.6 启用总记录数统计
            sourceBuilder.trackTotalHits(true);

            // 4. 将搜索源构建器设置到搜索请求中
            searchRequest.source(sourceBuilder);

            // 5. 向Elasticsearch发起搜索请求
            SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
            // 6. 解析搜索结果
            // 6.1 设置总记录数
            result.setTotal(searchResponse.getHits().getTotalHits().value);
            // 6.2 计算并设置总页数
            result.setPages(EsSearchUtils.calculateTotalPages(searchResponse.getHits().getTotalHits().value, query.getPageSize()));

            // 7. 解析搜索结果中的每条记录
            List<ItemDoc> list = new ArrayList<>();
            for (SearchHit hit : searchResponse.getHits().getHits()) {
                // 7.1 将搜索结果转换为商品文档对象
                ItemDoc itemDoc = JSONUtil.toBean(hit.getSourceAsString(), ItemDoc.class);
                // 7.2 处理高亮字段, 重新设置对象itemDoc中商品的名称字段
                setHighlightFields(itemDoc, hit.getHighlightFields());
                list.add(itemDoc);
            }
            // 8. 将商品文档列表设置到分页结果中
            result.setList(list);

            log.info("查询ES成功,查询结果:{}", result);
        } catch (IOException e) {
            // 9. 捕获并记录异常
            log.error("查询ES失败,出现异常", e);
        } finally {
            try {
                // 10. 关闭Elasticsearch客户端连接
                restHighLevelClient.close();
            } catch (IOException e) {
                log.error("连接关闭异常", e);
                e.printStackTrace();
            }
        }
        // 10. 返回分页结果
        return result;
    }

    /**
     * 构建查询条件
     *
     * @param query 搜索查询参数封装对象
     * @return BoolQueryBuilder 查询条件构造器
     */
    public BoolQueryBuilder buildBoolQuery(ItemPageQuery query) {
        // 复合条件构造器-boolQuery
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        // 搜索关键字
        if (StringUtils.isNotBlank(query.getKey())) {
            boolQuery.must(QueryBuilders.matchQuery("name", query.getKey()));
        }
        // 分类过滤
        if (StringUtils.isNotBlank(query.getCategory())) {
            boolQuery.filter(QueryBuilders.termQuery("category", query.getCategory()));
        }
        // 品牌过滤
        if (StringUtils.isNotBlank(query.getBrand())) {
            boolQuery.filter(QueryBuilders.termQuery("brand", query.getBrand()));
        }
        // 价格范围过滤（增强校验）
        if (isValidPriceRange(query.getMinPrice(), query.getMaxPrice())) {
            boolQuery.filter(QueryBuilders.rangeQuery("price")
                    .gte(query.getMinPrice())
                    .lte(query.getMaxPrice()));
        }
        return boolQuery;
    }

    /**
     * 判断价格范围是否有效
     * @param min
     * @param max
     * @return
     */
    private boolean isValidPriceRange(Integer min, Integer max) {
        if (min == null || max == null) return false;
        return min >= 0 && max >= 0 && max >= min;
    }

    /**
     * 构建高亮设置
     *
     * @param field 需要高亮显示的字段名
     * @return HighlightBuilder 高亮构造器
     */
    public HighlightBuilder buildHighlighter(String field) {
        return new HighlightBuilder()
                .field(field)
                .preTags("<em>")
                .postTags("</em>");
    }

    /**
     * 解析高亮字段并设置到文档对象中
     *
     * @param itemDoc         文档对象
     * @param highlightFields 高亮字段Map
     */
    public void setHighlightFields(ItemDoc itemDoc, Map<String, HighlightField> highlightFields) {
        if (highlightFields != null && !highlightFields.isEmpty()) {
            HighlightField nameHighlight = highlightFields.get("name");
            if (nameHighlight != null && nameHighlight.getFragments() != null && nameHighlight.getFragments().length > 0) {
                itemDoc.setName(nameHighlight.getFragments()[0].string());
            }
        }
    }


    /**
     * 获得分类和品牌的聚合值
     * 该方法通过Elasticsearch的聚合功能，获取商品分类和品牌的聚合结果。
     *
     * @param query 包含搜索条件的查询对象，用于过滤商品数据
     * @return CategoryAndBrandVo 包含分类和品牌聚合结果的对象
     */
    @Override
    public CategoryAndBrandVo getFilters(ItemPageQuery query) {
        try (RestHighLevelClient client = EsSearchUtils.createClient("http://192.168.88.133:9200")) {
            SearchRequest request = new SearchRequest("items");
            BoolQueryBuilder boolQueryBuilder = buildBoolQuery(query);

            /*
             * 构建聚合请求
             * 构建并配置一个Elasticsearch的搜索请求源（SearchSourceBuilder）。
             * 该请求源用于执行一个聚合查询，查询结果不返回文档（size=0），
             * 而是返回两个聚合结果：按类别（category）和品牌（brand）的聚合统计。
             */
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder()
                    .query(boolQueryBuilder)  // 设置查询条件
                    .size(0)  // 设置返回的文档数量为0，仅返回聚合结果
                    .aggregation(AggregationBuilders.terms("category_agg").field("category").size(100))  // 按类别进行聚合，最多返回100个类别
                    .aggregation(AggregationBuilders.terms("brand_agg").field("brand").size(100));  // 按品牌进行聚合，最多返回100个品牌
            request.source(sourceBuilder);  // 将配置好的搜索请求源应用到请求对象中

            // 执行查询
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            log.info("查询结果:{}", response);
            return parseAggregationResponse(response);
        } catch (IOException e) {
            log.error("聚合查询失败", e);
            return new CategoryAndBrandVo();
        }
    }

    /**
     * 解析聚合查询结果
     * 该方法从Elasticsearch的聚合查询响应中提取分类和品牌的聚合结果，并将其封装到CategoryAndBrandVo对象中。
     *
     * @param response Elasticsearch的聚合查询响应对象
     * @return CategoryAndBrandVo 包含分类和品牌聚合结果的对象
     */
    private CategoryAndBrandVo parseAggregationResponse(SearchResponse response) {
        CategoryAndBrandVo vo = new CategoryAndBrandVo();
        Aggregations aggs = response.getAggregations();

        // 解析分类聚合
        Terms categoryTerms = aggs.get("category_agg");
        vo.setCategory(categoryTerms.getBuckets().stream()
                .map(Terms.Bucket::getKeyAsString) // 获取分类名称
                .collect(Collectors.toList()));

        // 解析品牌聚合
        Terms brandTerms = aggs.get("brand_agg");
        vo.setBrand(brandTerms.getBuckets().stream()
                .map(Terms.Bucket::getKeyAsString) // 获取品牌名称
                .collect(Collectors.toList()));

        return vo;
    }


    /**
     * 获得分类和品牌的聚合值
     * 该方法通过Elasticsearch的聚合功能，获取商品分类和品牌的聚合结果。
     *
     * @param query 包含搜索条件的查询对象，用于过滤商品数据
     * @return CategoryAndBrandVo 包含分类和品牌聚合结果的对象
     */
    public CategoryAndBrandVo getFiltersBefore(ItemPageQuery query) {
        try {
            // 1. 模拟延迟，确保Elasticsearch集群有足够时间处理请求
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 2. 创建Elasticsearch客户端连接
        restHighLevelClient = EsSearchUtils.createClient("http://192.168.88.133:9200");

        // 3. 初始化返回对象
        CategoryAndBrandVo categoryAndBrandVo = new CategoryAndBrandVo();

        // 4. 创建搜索请求对象，指定索引名称为"items"
        SearchRequest request = new SearchRequest("items");

        // 5. 构建布尔查询条件
        BoolQueryBuilder boolQueryBuilder = buildBoolQuery(query);

        // 6. 设置查询条件，并设置返回结果大小为0（仅返回聚合结果）
        request.source().query(boolQueryBuilder).size(0);

        // 7. 添加分类聚合
        request.source().aggregation(
                AggregationBuilders.terms("category_agg").field("category").size(10)
        );

        // 8. 添加品牌聚合
        request.source().aggregation(
                AggregationBuilders.terms("brand_agg").field("brand").size(10));

        // 9. 初始化分类和品牌列表

        List<String> categoryList = new ArrayList<>();
        List<String> brandList = new ArrayList<>();

        // 10. 发送请求并处理响应
        try {
            SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
            Aggregations aggregations = response.getAggregations();

            // 11. 处理分类聚合结果
            Terms categoryTerms = aggregations.get("category_agg");
            List<? extends Terms.Bucket> buckets = categoryTerms.getBuckets();
            for (Terms.Bucket bucket : buckets) {
                String category = bucket.getKeyAsString();
                categoryList.add(category);
            }

            // 12. 处理品牌聚合结果
            Terms brandTerms = aggregations.get("brand_agg");
            List<? extends Terms.Bucket> buckets1 = brandTerms.getBuckets();
            for (Terms.Bucket bucket : buckets1) {
                String brand = bucket.getKeyAsString();
                brandList.add(brand);
            }
        } catch (IOException e) {
            log.error("发送请求异常");
        }

        // 13. 设置分类和品牌列表到返回对象中
        categoryAndBrandVo.setCategory(categoryList);
        categoryAndBrandVo.setBrand(brandList);

        log.info("分类和品牌聚合结果：{}", categoryAndBrandVo);

        // 14. 关闭Elasticsearch客户端连接
        try {
            restHighLevelClient.close();
        } catch (IOException e) {
            log.error("关闭连接异常");
        }

        // 15. 返回结果
        return categoryAndBrandVo;
    }

}
